#!/usr/bin/env python
"""
Module for parsing a secondary structure
"""
import os
import pyrna.ply.lex as lex
import pyrna.ply.yacc as yacc
from .alphabet import UnambiguousRNAAlphabet
from .grammar import *

###############################################################################

class Parser(object):
    """
    Base class for a lexer/parser that has the rules defined as methods.
    """
    tokens = ()
    precedence = ()

    def __init__(self, **kwargs):
        self._debug = kwargs.get('debug', 0)
        self._names = dict()
        try:
            modName = os.path.split(os.path.splitext(__file__)[0])[1] + \
                                    "_" + self.__class__.__name__
        except:
            modName = "parser" + "_" + self.__class__.__name__
        self._debugFile = modName + ".dbg"
        self._tabModule = modName + "_" + "parsetab"

        # Build the lexer and parser
        self._lexer  = lex.lex(module = self,
                               debug = self._debug)
        self._parser = yacc.yacc(module = self,
                                 debug = self._debug,
                                 debugfile = self._debugFile,
                                 tabmodule = self._tabModule)

    def parse(self, structure):
        """
        Abstract method.
        """
        raise NotImplementedError

###############################################################################

class RNAStructureParser(Parser):
    """
    RNA structure parser:
    Construct a grammar for a RNA secondary structure.
    """
    
    def __init__(self, **kwargs):
        """
        Initialize this RNAStructureParser.
        """
        super(RNAStructureParser, self).__init__(**kwargs)
        self._axiom = None
        self._productions = list()
        self._alphabet = UnambiguousRNAAlphabet()
        self._nonTerminalGenerator = NonTerminalGenerator()

    # List of token names.
    tokens = (
        'BASE',
        'L_PAREN',
        'R_PAREN',
        'ANY',
        )

    # Regular expression rules for simple tokens
    t_L_PAREN   = r'\('
    t_R_PAREN   = r'\)'
    t_BASE      = r'[acgu]'
    t_ANY       = r'\.'

    # Define a rule so we can track line numbers
    def t_newline(self, t):
        r'\n+'
        t.lexer.lineno += len(t.value)
    
    # A string containing ignored characters (spaces and tabs)
    t_ignore  = ' \t'
    
    # Error handling rule
    def t_error(self, t):
        print("Illegal character '%s'" % t.value[0])
        t.lexer.skip(1)

    def _generateComplementaryBases(self,
                                    leftHandSide,
                                    middleNonTerminal = None,
                                    rightmostNonTerminal = None):
        """
        Add all productions for complementary bases.
        """
        for base in self._alphabet:
            for complementaryBase in self._alphabet.complementaryBases(base):
                rightHandSide = [Terminal(base)]
                if middleNonTerminal is not None:
                    rightHandSide.append(middleNonTerminal)
                rightHandSide.append(Terminal(complementaryBase))
                if rightmostNonTerminal is not None:
                    rightHandSide.append(rightmostNonTerminal)
                production = Production(leftHandSide, rightHandSide)
                self._productions.append(production)

    def p_axiom(self, p):
        'axiom : structure'
        leftHandSide = self._nonTerminalGenerator()
        production = Production(leftHandSide, [p[1]])
        self._productions.append(production)
        self._axiom = leftHandSide
        p[0] = p[1]
        
    def p_structure_paren1(self, p):
        'structure : L_PAREN structure R_PAREN structure'
        leftHandSide = self._nonTerminalGenerator()
        self._generateComplementaryBases(leftHandSide,
                                         middleNonTerminal = p[2],
                                         rightmostNonTerminal = p[4])
        p[0] = leftHandSide

    def p_structure_paren2(self, p):
        'structure : L_PAREN structure R_PAREN'
        leftHandSide = self._nonTerminalGenerator()
        self._generateComplementaryBases(leftHandSide,
                                         middleNonTerminal = p[2])
        p[0] = leftHandSide

    def p_structure_paren3(self, p):
        'structure : L_PAREN R_PAREN structure'
        leftHandSide = self._nonTerminalGenerator()
        self._generateComplementaryBases(leftHandSide,
                                         rightmostNonTerminal = p[3])
        p[0] = leftHandSide

    def p_structure_paren4(self, p):
        'structure : L_PAREN R_PAREN'
        leftHandSide = self._nonTerminalGenerator()
        self._generateComplementaryBases(leftHandSide)
        p[0] = leftHandSide
    
    def p_structure_base_1(self, p):
        'structure : BASE structure'
        leftHandSide = self._nonTerminalGenerator()
        rightHandSide = [Terminal(p[1]), p[2]]
        production = Production(leftHandSide, rightHandSide)
        self._productions.append(production)
        p[0] = leftHandSide
    
    def p_structure_base_2(self, p):
        'structure : BASE'
        leftHandSide = self._nonTerminalGenerator()
        rightHandSide = [Terminal(p[1])]
        production = Production(leftHandSide, rightHandSide)
        self._productions.append(production)
        p[0] = leftHandSide

    def p_one_character_1(self, p):
        'structure : ANY structure'
        leftHandSide = self._nonTerminalGenerator()
        for base in self._alphabet:
            rightHandSide = [Terminal(base), p[2]]
            production = Production(leftHandSide, rightHandSide)
            self._productions.append(production)
        p[0] = leftHandSide

    def p_one_character_2(self, p):
        'structure : ANY'
        leftHandSide = self._nonTerminalGenerator()
        for base in self._alphabet:
            rightHandSide = [Terminal(base)]
            production = Production(leftHandSide, rightHandSide)
            self._productions.append(production)
        p[0] = leftHandSide
        
    def p_error(self, p):
        """
        Error rule for syntax errors.
        """
        raise yacc.YaccError("Syntax error").with_traceback(p)

    def parse(self, structure):
        """
        Return a grammar.
        """
        self._parser.parse(structure, lexer = self._lexer)
        return (self._axiom, self._productions)

###############################################################################

def parseRNAStructure(structure):
    """
    Return a CFG Grammar.
    """
    parser = RNAStructureParser()
    (axiom, productions) = parser.parse(structure)
    return Grammar(axiom, productions)

###############################################################################

if __name__ == '__main__':
    structure = 'a'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    structure = 'acgu'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    structure = '.'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    structure = 'a.a'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    structure = '()'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    structure = '((()))'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))

    structure = '((((...))))'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    rna = parseRNAStructure(structure)
    
    structure = '(((.(...))))'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    
    structure = '((a.(...))u.)'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
    
    structure = '((.(...))..)'
    print('structure: %s' % structure)
    print(parseRNAStructure(structure))
        
    structure = '((..)).((.))'
    print('structure: %s' % structure)
    g = parseRNAStructure(structure)
    print(g.productions())
    print(g.productions(rightHandSide=g.productions()[1].rightHandSide()))
    
    
